/********************************************************************+
# Copyright 2018-2019 Daniel 'grindhold' Brendle
#
# This file is part of phex.
#
# phex is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation, either
# version 3 of the License, or (at your option) any later
# version.
#
# phex is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public
# License along with phex.
# If not, see http://www.gnu.org/licenses/.
*********************************************************************/

#include "mol_view.h"

const double RADIUS = 0.005;


struct _Phxvec {
    double x;
    double y;
    double z;
} typedef Phxvec;

void _get_sphere_point(float u, float v, Phxvec* target) {
    target->x += cos(u)*sin(v)*RADIUS;
    target->y += cos(v)*RADIUS;
    target->z += sin(u)*sin(v)*RADIUS;
    
}

void _generate_sphere(int res_u, int res_v, float x, float y, float z, float* vertices, float* colors) {
    float start_u = 0;
    float start_v = 0;
    float endU=M_PI*2;
    float endV=M_PI;
    float stepU=(endU-start_u)/res_u;
    float stepV=(endV-start_v)/res_v;
    Phxvec p0={x,y,z};
    Phxvec p1={x,y,z};
    Phxvec p2={x,y,z};
    Phxvec p3={x,y,z};

    int vert_offset = 0;
    for(int i = 0; i < res_u; i++){
        for(int j=0; j<res_v; j++){
            float u=i*stepU+start_u;
            float v=j*stepV+start_v;
            float un=(i+1==res_u) ? endU : (i+1)*stepU+start_u;
            float vn=(j+1==res_v) ? endV : (j+1)*stepV+start_v;
            _get_sphere_point(u, v, &p0);
            _get_sphere_point(u, vn, &p1);
            _get_sphere_point(un, v, &p2);
            _get_sphere_point(un, vn, &p3);
            int vio = 0;
            *(vertices + vert_offset + vio++) = p0.x;
            *(vertices + vert_offset + vio++) = p0.y;
            *(vertices + vert_offset + vio++) = p0.z;

            *(vertices + vert_offset + vio++) = p2.x;
            *(vertices + vert_offset + vio++) = p2.y;
            *(vertices + vert_offset + vio++) = p2.z;

            *(vertices + vert_offset + vio++) = p1.x;
            *(vertices + vert_offset + vio++) = p1.y;
            *(vertices + vert_offset + vio++) = p1.z;

            *(vertices + vert_offset + vio++) = p3.x;
            *(vertices + vert_offset + vio++) = p3.y;
            *(vertices + vert_offset + vio++) = p3.z;

            *(vertices + vert_offset + vio++) = p1.x;
            *(vertices + vert_offset + vio++) = p1.y;
            *(vertices + vert_offset + vio++) = p1.z;

            *(vertices + vert_offset + vio++) = p2.x;
            *(vertices + vert_offset + vio++) = p2.y;
            *(vertices + vert_offset + vio++) = p2.z;
            vert_offset += 18;
        }
    }
}

void _generate_geometry(GLData* gld) {
    gint64* coords = malloc(gld->ctx->coord_len*sizeof(gint64));
    for (int i = 0; i < gld->ctx->coord_len; i++) {
        *(coords+i) = (double)elm_slider_value_get(gld->ctx->coord_sliders[i]);
    }
    int n_atoms = 0;

    PhexfileAtom** geometry = phexfile_simulation_get_geometry_struct(
        gld->ctx->sim, coords, gld->ctx->coord_len, &n_atoms
    );

    free(coords);

    // Normalize atom coords to -0.5 - +0.5 box
    double maxval = 0;
    double cmpval = 0;
    for (int i = 0; i < n_atoms; i++) {
        cmpval = (*(geometry+i))->x;
        cmpval = cmpval < 0 ? cmpval * -1 : cmpval;
        maxval = cmpval > maxval ? cmpval : maxval;
        cmpval = (*(geometry+i))->y;
        cmpval = cmpval < 0 ? cmpval * -1 : cmpval;
        maxval = cmpval > maxval ? cmpval : maxval;
        cmpval = (*(geometry+i))->z;
        cmpval = cmpval < 0 ? cmpval * -1 : cmpval;
        maxval = cmpval > maxval ? cmpval : maxval;
    }
    for (int i = 0; i < n_atoms; i++) {
        (*(geometry + i))->x /= 2 * maxval;
        (*(geometry + i))->y /= 2 * maxval;
        (*(geometry + i))->z /= 2 * maxval;
    }

    int sphere_res = 4;
    int sphere_points = sphere_res * sphere_res * 6;

    gld->vertices_size = n_atoms * sphere_points * 3 * sizeof(float);
    gld->colors_size = n_atoms * sphere_points * 4 * sizeof(float);

    float* result = realloc(gld->vertices, gld->vertices_size);
    float* colors = realloc(gld->colors, gld->colors_size);

    gld->vertices = result;
    gld->colors = colors;

    for (int i = 0; i < n_atoms; i++) {
        _generate_sphere(sphere_res,sphere_res,(float)(*(geometry + i))->x,(float)(*(geometry + i))->y,(float)(*(geometry + i))->z,(result+i*sphere_points*3), NULL);

        char* atom = (*(geometry +i))->atom;
        float copycolor[] = {0.0,0.0,0.0,0.0};
        if        (strcmp(atom,"H") == 0) {
            copycolor[0] = 0.95; copycolor[1] = 0.95; copycolor[2] = 0.95; copycolor[3] = 1.0;
        } else if (strcmp(atom,"C") == 0) {
            copycolor[0] = 0.22; copycolor[1] = 0.22; copycolor[2] = 0.22; copycolor[3] = 1.0;
        } else if (strcmp(atom,"O") == 0) {
            copycolor[0] = 0.98; copycolor[1] = 0.14; copycolor[2] = 0.08; copycolor[3] = 1.0;
        } else if (strcmp(atom, "N") == 0) {
            copycolor[0] = 0.12; copycolor[1] = 0.22; copycolor[2] = 1.0; copycolor[3] = 1.0;
        } else if (strcmp(atom, "S") == 0) {
            copycolor[0] = 0.89; copycolor[1] = 0.89; copycolor[2] = 1.0; copycolor[3] = 1.0;
        } else {
            copycolor[0] = 1.0; copycolor[1] = 0.0; copycolor[2] = 1.0; copycolor[3] = 1.0;
        }
        for (int c = 0; c < sphere_points; c++) {
            memcpy(colors + i*sphere_points*4 + c*4, copycolor, sizeof(float)*4);
        }
    }
    g_free(geometry);
}

void
_init_mol_gl(Evas_Object *obj)
{
   int w, h;
   GLData *gld = evas_object_data_get(obj, "graph_gld");
   Evas_GL_API *gl = gld->glapi;

   elm_glview_size_get(obj, &w, &h);

   if (!gld->initialized)
     {
        if (!init_shaders(gld))
          {
             printf("Error Initializing Shaders\n");
             return;
          }

        gld->vertices = malloc(sizeof(float));
        gld->colors = malloc(sizeof(float));
        gld->vertices_size = 1;
        gld->colors_size = 1;

        gl->glGenBuffers(1, &gld->vertexID);
        gl->glBindBuffer(GL_ARRAY_BUFFER, gld->vertexID);
        gl->glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

        gl->glGenBuffers(1, &gld->colorID);
        gl->glBindBuffer(GL_ARRAY_BUFFER, gld->colorID);
        gl->glBufferData(GL_ARRAY_BUFFER, sizeof(colors), colors, GL_STATIC_DRAW);

        _generate_geometry(gld);
        _generate_geometry(gld);

        gl->glGenBuffers(1, &gld->vertexID2);
        gl->glBindBuffer(GL_ARRAY_BUFFER, gld->vertexID2);
        gl->glBufferData(GL_ARRAY_BUFFER, gld->vertices_size, gld->vertices, GL_STATIC_DRAW);

        gl->glGenBuffers(1, &gld->colorID2);
        gl->glBindBuffer(GL_ARRAY_BUFFER, gld->colorID2);
        gl->glBufferData(GL_ARRAY_BUFFER, gld->colors_size, gld->colors, GL_STATIC_DRAW);

        gld->initialized = EINA_TRUE;
     }
}


void
_draw_mol_gl(Evas_Object *obj)
{
   Evas_GL_API *gl = elm_glview_gl_api_get(obj);
   GLData *gld = evas_object_data_get(obj, "graph_gld");
   if(!gld) return;
   int w, h;
   int r, g, b, a;
   //scale
   /*double scalex = elm_slider_value_get(gld->slx);
   double scaley = elm_slider_value_get(gld->sly);
   double scalez = elm_slider_value_get(gld->slz);*/
   double scalex=0.75, scaley=0.75, scalez = 0.75;

   r = 10; g = 10; b = 10; a = 255;

   elm_glview_size_get(obj, &w, &h);

   gl->glClearDepthf(1.0f);
   gl->glClearColor(r / 255.0, g / 255.0, b / 255.0, a / 255.0);
   //gl->glEnable(GL_CULL_FACE);

   gl->glViewport(0, 0, w, h);
   gl->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

   gl->glUseProgram(gld->program);

   gl->glEnableVertexAttribArray(gld->positionLoc);
   gl->glBindBuffer(GL_ARRAY_BUFFER, gld->vertexID);
   gl->glVertexAttribPointer(gld->positionLoc, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), 0);

   gl->glEnableVertexAttribArray(gld->colorLoc);
   gl->glBindBuffer(GL_ARRAY_BUFFER, gld->colorID);
   gl->glVertexAttribPointer(gld->colorLoc, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 0);

   customLoadIdentity(gld->model);
   gld->yangle += 0.2;
   customRotate(gld->model, gld->xangle, gld->yangle, gld->zangle);
   //scale
   customScale(gld->model, scalex, scaley, scalez);
   customMutlMatrix(gld->mvp, gld->view, gld->model);

   gl->glUniformMatrix4fv(gld->mvpLoc, 1, GL_FALSE, gld->mvp);
   gl->glEnable(GL_BLEND);
   gl->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);


   gl->glDrawArrays(GL_TRIANGLES, 0, 6);

    _generate_geometry(gld);
    _generate_geometry(gld);

    gl->glDeleteBuffers(1, &gld->vertexID2);

    gl->glGenBuffers(1, &gld->vertexID2);
    gl->glBindBuffer(GL_ARRAY_BUFFER, gld->vertexID2);
    gl->glBufferData(GL_ARRAY_BUFFER, gld->vertices_size, gld->vertices, GL_STATIC_DRAW);

    gl->glDeleteBuffers(1, &gld->colorID2);
    gl->glGenBuffers(1, &gld->colorID2);
    gl->glBindBuffer(GL_ARRAY_BUFFER, gld->colorID2);
    gl->glBufferData(GL_ARRAY_BUFFER, gld->colors_size, gld->colors, GL_STATIC_DRAW);

   gl->glEnableVertexAttribArray(gld->positionLoc);
   gl->glBindBuffer(GL_ARRAY_BUFFER, gld->vertexID2);
   gl->glVertexAttribPointer(gld->positionLoc, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), 0);


   gl->glEnableVertexAttribArray(gld->colorLoc);
   gl->glBindBuffer(GL_ARRAY_BUFFER, gld->colorID2);
   gl->glVertexAttribPointer(gld->colorLoc, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 0);

   gl->glUniformMatrix4fv(gld->mvpLoc, 1, GL_FALSE, gld->mvp);
   gl->glDrawArrays(GL_TRIANGLES, 0, gld->vertices_size / 12);
   gl->glFlush();
}
